package driver

import (
	"context"
	"database/sql"
	"fmt"
	"gf-vben-demo/consts"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/errors/gcode"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/os/gtime"
	"github.com/gogf/gf/v2/util/gconv"
	"reflect"
	"strings"
)

// MyDriver is a custom database driver, which is used for testing only.
// For simplifying the unit testing case purpose, MyDriver struct inherits the mysql driver
// gdb.DriverMysql and overwrites its functions DoQuery and DoExec.
// So if there's any sql execution, it goes through MyDriver.DoQuery/MyDriver.DoExec firstly
// and then gdb.DriverMysql.DoQuery/gdb.DriverMysql.DoExec.
// You can call it sql "HOOK" or "HiJack" as your will.
type MyDriver struct {
	*gdb.DriverMysql
}

var (
	// customDriverName is my driver name, which is used for registering.
	customDriverName = "mysql"
)

func init() {
	// It here registers my custom driver in package initialization function "init".
	// You can later use this type in the database configuration.
	if err := gdb.Register(
		customDriverName,
		&MyDriver{},
	); err != nil {
		panic(any(err))
	}
}

// New creates and returns a database object for mysql.
// It implements the interface of gdb.Driver for extra database driver installation.
func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
	return &MyDriver{
		&gdb.DriverMysql{
			Core: core,
		},
	}, nil
}

//DoInsert 填充字段
func (d *MyDriver) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) {
	now := gtime.Now()
	for _, m := range list {
		m[consts.CreateDate] = now
		m[consts.StateColumn] = consts.DefaultState
		m[consts.VersionNumberColumn] = consts.DefaultVersion
	}
	return d.DriverMysql.DoInsert(ctx, link, table, list, option)
}

//DoUpdate 填充字段
func (d *MyDriver) DoUpdate(ctx context.Context, link gdb.Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
	table = d.QuotePrefixTableName(table)
	var (
		rv   = reflect.ValueOf(data)
		kind = rv.Kind()
	)
	if kind == reflect.Ptr {
		rv = rv.Elem()
		kind = rv.Kind()
	}
	var (
		params  []interface{}
		updates = ""
	)
	switch kind {
	case reflect.Map, reflect.Struct:
		var (
			fields         []string
			dataMap        = d.DriverMysql.ConvertDataForRecord(ctx, data)
			counterHandler = func(column string, counter gdb.Counter) {
				if counter.Value != 0 {
					column = d.QuoteWord(column)
					var (
						columnRef = d.QuoteWord(counter.Field)
						columnVal = counter.Value
						operator  = "+"
					)
					if columnVal < 0 {
						operator = "-"
						columnVal = -columnVal
					}
					fields = append(fields, fmt.Sprintf("%s=%s%s?", column, columnRef, operator))
					params = append(params, columnVal)
				}
			}
		)

		//更新版本号
		dataMap[consts.VersionNumberColumn] = gdb.Counter{Field: consts.VersionNumberColumn, Value: 1}
		//填充更新时间
		dataMap[consts.ModifyDate] = gtime.Now()
		for k, v := range dataMap {
			switch value := v.(type) {
			case *gdb.Counter:
				counterHandler(k, *value)
			case gdb.Counter:
				counterHandler(k, value)
			default:
				if s, ok := v.(gdb.Raw); ok {
					fields = append(fields, d.QuoteWord(k)+"="+gconv.String(s))
				} else {
					fields = append(fields, d.QuoteWord(k)+"=?")
					params = append(params, v)
				}
			}
		}
		updates = strings.Join(fields, ",")
	default:
		updates = gconv.String(data)
	}
	if len(updates) == 0 {
		return nil, gerror.NewCode(gcode.CodeMissingParameter, "data cannot be empty")
	}
	if len(params) > 0 {
		args = append(params, args...)
	}
	// If no link passed, it then uses the master link.
	if link == nil {
		if link, err = d.MasterLink(); err != nil {
			return nil, err
		}
	}
	result, err = d.DriverMysql.DoExec(ctx, link, fmt.Sprintf("UPDATE %s SET %s%s", table, updates, condition), args...)
	if err != nil {
		return nil, err
	}
	affected, err := result.RowsAffected()
	if err != nil {
		return nil, err
	}
	if affected < 1 {
		return nil, gerror.NewCode(gcode.CodeValidationFailed, "记录不存在或版本不一致")
	}
	return result, err
}
